package tms.model;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

/**
 * The Task class represents a task that can have subtasks
 * It extends AbstractModelObject and implements PropertyChangeListener
 * to allow the fire of property changes
 * 
 * @author Rolf Schmidt
 * @version 1.0
 * 
 */
public class Task extends AbstractModelObject implements PropertyChangeListener {

	/**
	 * ID of the task
	 */
	private int id;
	/**
	 * title of the task
	 */
	private String title;
	/**
	 * short description of the task
	 */
	private String description;
	/**
	 * contains the duration of this task in working days
	 */
	private int duration;
	/**
	 * contains the description of the deliverable(s) associated with this task
	 */
	private String deliverable;
	/**
	 * contains the deadline of this task
	 */
	private Date deadline;
	/**
	 * contains the name of the person associated with this task
	 */
	private String person;
	/**
	 * a list of subtasks
	 */
	private ArrayList<Task> subtasks;
	/**
	 * parent of the task
	 */
	private Task parent;
	/**
	 * ID of the Task that can only be started after this task has been finished
	 */
	private int beforeId;

	/**
	 * default constructor
	 */
	public Task() {
		super();
		this.id = 0;
		this.title = "none";
		this.description = "none";
		this.duration = 1; // as this is the default set in setDuration()
		this.deliverable = "none";
		this.deadline = Calendar.getInstance().getTime();
		this.person = "nobody";
		this.beforeId = -1;
		subtasks = new ArrayList<Task>();
		this.parent = null;
	}

	/**
	 * @return the beforeId
	 */
	public int getBeforeId() {
		return beforeId;
	}

	/**
	 * @param beforeId
	 *            the beforeId to set
	 */
	public void setBeforeId(int beforeId) {
		// subtasks cannot have a beforeId
		if (!hasParent()) {
			if (beforeId != id) {
				if (beforeId < 0) {
					beforeId = -1;
				}
				int oldValue = this.beforeId;
				this.beforeId = beforeId;
				firePropertyChange("beforeId", oldValue, this.beforeId);
			}
		} else {
			this.beforeId = -1;
		}
	}

	/**
	 * getter of the id of the task
	 * 
	 * @return the id
	 */
	public int getId() {
		return id;
	}

	/**
	 * setter of the id of the task
	 * if the new id is less than zero, then a default value (zero) is assigned
	 * to the task id
	 * Also it fires an id change property event
	 * 
	 * @param id
	 *            the id to set
	 */
	public void setId(int id) {
		int oldValue = this.id;
		if (id < 0) {
			this.id = 0;
		} else {
			this.id = id;
		}
		firePropertyChange("id", oldValue, this.id);
	}

	/**
	 * getter of the title of the task
	 * 
	 * @return the title
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * setter of the title of the task
	 * if the new title is null or empty , then a default value (none) is
	 * assigned to the task title
	 * Also it fires a title change property event
	 * 
	 * @param title
	 *            the title to set
	 */
	public void setTitle(String title) {
		String oldValue = this.title;
		if (title == null || title.equals("")) {
			this.title = "none";
		} else {
			this.title = title;
		}
		firePropertyChange("title", oldValue, this.title);
	}

	/**
	 * getter of the description of the task
	 * 
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * setter of the description of the task
	 * if the new description is null or empty , then a default value (none) is
	 * assigned to the task description
	 * Also it fires a description change property event
	 * 
	 * @param description
	 *            the description to set
	 */
	public void setDescription(String description) {
		String oldValue = this.description;
		if (description == null || description.equals("")) {
			this.description = "none";
		} else {
			this.description = description;
		}
		firePropertyChange("description", oldValue, this.description);
	}

	/**
	 * getter of the duration of the task
	 * 
	 * @return the duration
	 */
	public int getDuration() {
		return duration;
	}

	/**
	 * setter of the duration of the task
	 * recursively calculates the duration as the sum of the subtask durations
	 * if the sum is greater than the duration value
	 * if the new duration is less than zero , then a default value (1) is
	 * assigned to the task duration
	 * Also it fires a duration change property event
	 * 
	 * @param duration
	 *            the duration to set
	 */
	public void setDuration(int duration) {
		int oldValue = this.duration;
		if (duration <= 0) {
			duration = 1; // every task takes at least one day (otherwise
								// it won't show up in the Gantt chart
		}
		int subtaskDuration = 0;
		// if there are subtasks, the duration needs to be calculated
		// as the sum of the subtask duration
		for (Task subtask : subtasks) {
			subtaskDuration += subtask.getDuration();
		}
		if (duration < subtaskDuration) {
			this.duration = subtaskDuration;
		} else {
			// the task does not have subtasks
			// therefore the duration can be set to the duration parameter
			this.duration = duration;
		}
		if (hasParent()) {
			// the parent duration needs to be checked and possibly changed
			// setDuration() recursively calculates the duration as the sum of
			// the subtask durations
			// if the sum is greater than the duration value
			getParent().setDuration(getParent().getDuration());
		}
		firePropertyChange("duration", oldValue, this.duration);
	}

	/**
	 * getter of the deliverable of the task
	 * 
	 * @return the deliverable
	 */
	public String getDeliverable() {
		return deliverable;
	}

	/**
	 * setter of the deliverable of the task
	 * if the new deliverable is null or empty , then a default value (none) is
	 * assigned to the task deliverable
	 * Also it fires a deliverable change property event
	 * 
	 * @param deliverable
	 *            the deliverable to set
	 */
	public void setDeliverable(String deliverable) {
		String oldValue = this.deliverable;
		if (deliverable == null || deliverable.equals("")) {
			this.deliverable = "none";
		} else {
			this.deliverable = deliverable;
		}
		firePropertyChange("deliverable", oldValue, this.deliverable);
	}

	/**
	 * getter of the deadline of the task
	 * 
	 * @return the deadline
	 */
	public Date getDeadline() {
		return deadline;
	}

	/**
	 * setter of the deadline of the task
	 * if the new deadline is null , then the old value is kept
	 * Also it fires a deadline change property event
	 * 
	 * @param deadline
	 *            the deadline to set
	 */
	public void setDeadline(Date deadline) {
		Date oldValue = this.deadline;
		if (deadline != null) {
			this.deadline = deadline;
		}
		firePropertyChange("deadline", oldValue, this.deadline);
	}

	/**
	 * getter of the person name assigned to the task
	 * 
	 * @return the person
	 */
	public String getPerson() {
		return person;
	}

	/**
	 * setter of the person name assigned to the task
	 * if the new person name is null or empty , then a default value (nobody)
	 * is assigned
	 * Also it fires a person change property event
	 * 
	 * @param person
	 *            the person to set
	 */
	public void setPerson(String person) {
		String oldValue = this.person;
		if (person == null || person.equals("")) {
			person = "nobody";
		}
		this.person = person;
		// the following code makes sure that only one person is in charge of a
		// task and all of its subtasks
		if (hasChildren()) {
			// change 'person' for all subtasks and their subtasks
			for (Task subtask : subtasks) {
				subtask.person = person;
				subtask.firePropertyChange("person", oldValue, subtask.person);
				setPersonForChildren(subtask, person);
			}
		}
		if (hasParent()) {
			// change 'person' for all sibling subtasks and their subtasks
			for (Task siblingTask : getParent().getSubtasks()) {
				if (siblingTask != this) {
					siblingTask.person = person;
					siblingTask.firePropertyChange("person", oldValue, siblingTask.person);
					setPersonForChildren(siblingTask, person);
				}
			}
			getParent().setPerson(person);
		}
		firePropertyChange("person", oldValue, this.person);
	}

	private void setPersonForChildren(Task parent, String person) {
		for (Task subtask : parent.getSubtasks()) {
			String oldValue = subtask.getPerson();
			subtask.person = person;
			subtask.firePropertyChange("person", oldValue, subtask.person);
			setPersonForChildren(subtask, person);
		}
	}

	/**
	 * @return the subtasks
	 */
	public ArrayList<Task> getSubtasks() {
		return subtasks;
	}

	/**
	 * @return the parent
	 */
	public Task getParent() {
		return parent;
	}

	/**
	 * @param parent
	 *            the parent to set
	 */
	public void setParent(Task parent) {

		if (parent == null)
			return;

		if (parent == this)
			return;

		if (parent.getId() == this.id)
			return;

		Task oldValue = this.parent;
		this.parent = parent;
		firePropertyChange("parent", oldValue, this.parent);
	}

	/**
	 * method to check if the task has subtasks
	 */
	public boolean hasChildren() {
		if (subtasks.size() > 0) {
			return true;
		}
		return false;
	}

	/**
	 * @return true if the Task is a subtask, false if the task is a task (i.e.
	 *         top-level)
	 */
	public boolean hasParent() {
		return getParent() != null;
	}

	/**
	 * method to recursively clear the subtasks
	 */
	public void clear() {
		if (hasChildren()) {
			for (Task task : subtasks) {
				task.clear();
			}
		}
		subtasks.clear();
	}

	/**
	 * method to add a subtask
	 */
	public void addSubtask(Task subtask) {

		if (subtask == null)
			return;

		if (subtask == this)
			return;

		if (subtask.getId() == this.id)
			return;

		subtask.setPerson(this.getPerson());
		subtask.setBeforeId(-1);

		if (subtasks.contains(subtask))
			return;

		int subtaskId = subtask.getId();
		for (Task tsubtask : subtasks) {
			int tid = tsubtask.getId();
			if (tid == subtaskId)
				return;
		}

		subtasks.add(subtask);

		int oldValue = this.duration;
		if (subtask.getDuration() > this.duration)
			this.duration = subtask.getDuration();

		subtask.setParent(this);
		subtask.addPropertyChangeListener(this);
		firePropertyChange("subtasks", null, this.subtasks);
		firePropertyChange("duration", oldValue, this.duration);
	}

	/**
	 * method to remove a subtask
	 */
	public void removeSubtask(Task subtask) {

		if (subtask == null)
			return;

		if (subtask == this)
			return;

		if (subtask.getId() == this.id)
			return;

		subtasks.remove(subtask);
		subtask.parent = null;
//		int oldValue = this.duration;
//		this.duration -= subtask.getDuration();
//		if (this.duration <= 0)
//			this.duration = 1;
		subtask.removePropertyChangeListener(this);
		firePropertyChange("subtasks", null, this.subtasks);
//		firePropertyChange("duration", oldValue, this.duration);
	}

	@Override
	/**
	 * this method gets called whenever there is a property change
	 * 
	 *  @param evt a PropertyChangeEvent object describing the event source and the property that has changed.
	 */
	public void propertyChange(PropertyChangeEvent evt) {

		if (evt.getPropertyName().equals("deadline")) {
			if (evt.getSource() instanceof Task) {
				Task task = (Task) evt.getSource();

				// the deadline of a subtask cannot be later than the deadline
				// of the parent task
				if (task.getDeadline().after(this.getDeadline())) {
					this.setDeadline(task.getDeadline());
				}
				firePropertyChange("deadline", null, this.deadline);
			}
		}
	}

}
